package com.gonsin.ta.logic.parse.condition;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.gonsin.ta.logic.TA;
import com.gonsin.ta.logic.enums.ConditionTypeEnum;
import com.gonsin.ta.logic.enums.TaResult;
import com.gonsin.ta.logic.exceptions.TaException;
import com.gonsin.ta.logic.node.SignalNodeAdapter;
import com.gonsin.ta.logic.parse.condition.context.ChainContextImpl;
import com.gonsin.ta.logic.parse.serialize.SerialResult;
import com.gonsin.ta.logic.parse.serialize.SerializeContext;
import com.gonsin.ta.logic.parse.serialize.TaDeserializable;
import com.gonsin.ta.logic.parse.serialize.WaitConditionSerial;
import com.gonsin.ta.logic.process.SignalCenter;
import com.google.gson.GsonBuilder;
import com.google.gson.annotations.Expose;
import freemarker.ext.beans.BeansWrapperBuilder;
import freemarker.template.Configuration;
import freemarker.template.Template;
import io.swagger.annotations.ApiModelProperty;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.*;

@Slf4j
public class WaitCondition extends Condition {

    /**
     * 信号组件
     */
    private SignalNodeAdapter signalNodeInstance;

    @ApiModelProperty("等待的信号值")
    private String signalNode;


    @ApiModelProperty("方法类型")
    private ConditionTypeEnum conditionType;

    /**
     * 超时时间
     */
    @ApiModelProperty("超时时间")
    private Long timeout;

    /**
     * 超时时间
     */
    @ApiModelProperty("超时时间的模板")
    private String timeoutModel;

    /**
     * 下一步操作
     */
    @ApiModelProperty("下一步操作")
    private List<Executable> next;


    /**
     * 超时时执行
     */
    @ApiModelProperty("超时时执行")
    private List<Executable> timeoutTask;

    /**
     * 信号中心
     */
    @JsonIgnore
    @Expose(serialize = false, deserialize = false)
    private transient SignalCenter signalCenter;

    /**
     * 带有超时时间的waiter方法
     * @param signalNodeInstance
     * @param timeout
     * @param signalCenter
     */
    public WaitCondition(SignalNodeAdapter signalNodeInstance, Long timeout, SignalCenter signalCenter) {
        this.signalNodeInstance = signalNodeInstance;
        this.timeout = timeout;
        this.signalCenter = signalCenter;
        this.signalNode = signalNodeInstance.getSignal();
        this.conditionType = ConditionTypeEnum.WAIT;
    }


    /**
     * 动态超时时间的wait方法
     * @param signalNodeInstance
     * @param timeoutModel
     * @param signalCenter
     */
    public WaitCondition(SignalNodeAdapter signalNodeInstance, String timeoutModel, SignalCenter signalCenter) {
        this.signalNodeInstance = signalNodeInstance;
        this.timeoutModel = timeoutModel;
        this.signalCenter = signalCenter;
        this.signalNode = signalNodeInstance.getSignal();
        this.conditionType = ConditionTypeEnum.WAIT;
    }

    public WaitCondition(SignalNodeAdapter signalNodeInstance, SignalCenter signalCenter) {
        this.signalNodeInstance = signalNodeInstance;
        this.timeout = null;
        this.signalCenter = signalCenter;
        this.signalNode = signalNodeInstance.getSignal();
        this.conditionType = ConditionTypeEnum.WAIT;
    }

    @Override
    public void execute(ChainContextImpl context) throws Exception {

        if(next == null){
            throw new TaException("chainId = {}中，WAIT方法后面必须调用THEN方法");
        }

        signalNodeInstance.execute(context);
        String signal = signalNodeInstance.getSignal();

//        ObjectMapper mapper = new ObjectMapper();
//        String nextJson = mapper.writeValueAsString(next);
//        String exceptionJson = timeoutTask == null ? null : mapper.writeValueAsString(timeoutTask);


        SerialResult nextExe = signalCenter.serialize(next);
        SerialResult timeoutExe = timeoutTask == null ? null : signalCenter.serialize(timeoutTask);

        Map<String, TaDeserializable> stack = new HashMap<>();
        if(nextExe.getStack() != null){
            stack.putAll(nextExe.getStack());
        }
        if(timeoutExe != null && timeoutExe.getStack() != null){
            stack.putAll(timeoutExe.getStack());
        }


        if(timeout == null){

            // 优先使用timeout，再使用timeoutModel
            if(timeoutModel != null){

                String dynamicTimeoutStr = generalString(timeoutModel, context.getValues());
                long dynamicTimeout = Long.parseLong(dynamicTimeoutStr);
                dynamicTimeout = dynamicTimeout * 1000;

                // 将信号持久化到数据库
                signalCenter.addSignal(context, signal, dynamicTimeout, nextExe.getExecutable(), timeoutExe == null ? null : timeoutExe.getExecutable(), stack);
                if(TA.LOG){
                    TA.log().info("TA - 流程实例【{}】正在等待信号【{}】", context.getInstanceId(), signal);
                }
            } else {

                // 将信号持久化到数据库
                signalCenter.addSignal(context, signal, null, nextExe.getExecutable(), timeoutExe == null ? null : timeoutExe.getExecutable(), stack);
                if(TA.LOG){
                    TA.log().info("TA - 流程实例【{}】正在等待信号【{}】", context.getInstanceId(), signal);
                }

            }
        } else {
            // 将信号持久化到数据库
            signalCenter.addSignal(context, signal, timeout * 1000, nextExe.getExecutable(), timeoutExe == null ? null : timeoutExe.getExecutable(), stack);
            if(TA.LOG){
                TA.log().info("TA - 流程实例【{}】正在等待信号【{}】", context.getInstanceId(), signal);
            }
        }



        // 设置状态
        context.setResult(TaResult.WAITING);
        context.setWaitingSignal(signal);
    }

    @Override
    public TaDeserializable toSerializable(SerializeContext serializeContext) {
        WaitConditionSerial conditionSerial = new WaitConditionSerial();
        conditionSerial.setConditionId(getConditionId());
        conditionSerial.setSignalNode(signalNode);

        List<TaDeserializable> executableList = new ArrayList<>();
        for(Executable exe : next){
            executableList.add(exe.toSerializable(serializeContext));
        }
        conditionSerial.setNext(executableList);

        executableList = new ArrayList<>();
        if(timeoutTask != null){
            for(Executable exe : timeoutTask){
                executableList.add(exe.toSerializable(serializeContext));
            }
            conditionSerial.setTimeoutTask(executableList);
        }

        conditionSerial.setTimeout(timeout);
        conditionSerial.setTimeoutModel(timeoutModel);

        return conditionSerial;
    }

    public String getSignalNode() {
        return signalNode;
    }

    public Long getTimeout() {
        return timeout;
    }

    public List<Executable> getNext() {
        return next;
    }

    public List<Executable> getTimeoutTask() {
        return timeoutTask;
    }

    public void setNext(List<Executable> next) {
        this.next = next;
    }

    public void setTimeoutTask(List<Executable> timeoutTask) {
        this.timeoutTask = timeoutTask;
    }

    public ConditionTypeEnum getConditionType() {
        return conditionType;
    }


    /**
     * 用freemarker框架注入字符串
     */
    private String generalString(String in, Object params) throws Exception {
        if(params == null){
            return in;
        }
        Configuration conf = new Configuration(Configuration.VERSION_2_3_22);
        conf.setObjectWrapper(new BeansWrapperBuilder(Configuration.VERSION_2_3_22).build());
        Template t = null;
        try {
            t = new Template("templateName", new StringReader(in), conf);
        } catch (IOException e) {
            e.printStackTrace();
        }
        Writer out = new StringWriter();
        t.process(params, out);
        String content = out.toString();
        return content;
    }

    @Override
    public List<FutureCondition> searchFutureConditions() {
        List<FutureCondition> conditions = new ArrayList<>();
        List<Executable> childs = new ArrayList<>();
        if(!CollectionUtils.isEmpty(next)){
            childs.addAll(next);
        }
        if(!CollectionUtils.isEmpty(timeoutTask)){
            childs.addAll(timeoutTask);
        }
        for(Executable executable : childs){
            if(executable instanceof FutureCondition){
                conditions.add((FutureCondition) executable);
                continue;
            }
            if(executable instanceof Condition){
                List<FutureCondition> childConditions = ((Condition)executable).searchFutureConditions();
                if(!CollectionUtils.isEmpty(childConditions)){
                    conditions.addAll(childConditions);
                }
            }
        }
        return conditions;
    }

    public void setTimeout(Long timeout) {
        this.timeout = timeout;
    }

    public void setTimeoutModel(String timeoutModel) {
        this.timeoutModel = timeoutModel;
    }
}
